/**
 * @class L.EditToolbar.Edit
 * @aka EditToolbar.Edit
 */
L.EditToolbar.Edit = L.Handler.extend({
    statics: {
        TYPE: 'edit'
    },

    // @method intialize(): void
    initialize: function (map, options) {
        L.Handler.prototype.initialize.call(this, map);

        L.setOptions(this, options);

        // Store the selectable layer group for ease of access
        this._featureGroup = options.featureGroup;

        if (!(this._featureGroup instanceof L.FeatureGroup)) {
            throw new Error('options.featureGroup must be a L.FeatureGroup');
        }

        this._uneditedLayerProps = {};

        // Save the type so super can fire, need to do this as cannot do this.TYPE :(
        this.type = L.EditToolbar.Edit.TYPE;

        var version = L.version.split('.');
        //If Version is >= 1.2.0
        if (parseInt(version[0], 10) === 1 && parseInt(version[1], 10) >= 2) {
            L.EditToolbar.Edit.include(L.Evented.prototype);
        } else {
            L.EditToolbar.Edit.include(L.Mixin.Events);
        }
    },

    // @method enable(): void
    // Enable the edit toolbar
    enable: function () {
        if (this._enabled || !this._hasAvailableLayers()) {
            return;
        }
        this.fire('enabled', {handler: this.type});
        //this disable other handlers

        this._map.fire(L.Draw.Event.EDITSTART, {handler: this.type});
        //allow drawLayer to be updated before beginning edition.

        L.Handler.prototype.enable.call(this);
        this._featureGroup
            .on('layeradd', this._enableLayerEdit, this)
            .on('layerremove', this._disableLayerEdit, this);
    },

    // @method disable(): void
    // Disable the edit toolbar
    disable: function () {
        if (!this._enabled) {
            return;
        }
        this._featureGroup
            .off('layeradd', this._enableLayerEdit, this)
            .off('layerremove', this._disableLayerEdit, this);
        L.Handler.prototype.disable.call(this);
        this._map.fire(L.Draw.Event.EDITSTOP, {handler: this.type});
        this.fire('disabled', {handler: this.type});
    },

    // @method addHooks(): void
    // Add listener hooks for this handler
    addHooks: function () {
        var map = this._map;

        if (map) {
            map.getContainer().focus();

            this._featureGroup.eachLayer(this._enableLayerEdit, this);

            this._tooltip = new L.Draw.Tooltip(this._map);
            this._tooltip.updateContent({
                text: L.drawLocal.edit.handlers.edit.tooltip.text,
                subtext: L.drawLocal.edit.handlers.edit.tooltip.subtext
            });

            // Quickly access the tooltip to update for intersection checking
            map._editTooltip = this._tooltip;

            this._updateTooltip();

            this._map
                .on('mousemove', this._onMouseMove, this)
                .on('touchmove', this._onMouseMove, this)
                .on('MSPointerMove', this._onMouseMove, this)
                .on(L.Draw.Event.EDITVERTEX, this._updateTooltip, this);
        }
    },

    // @method removeHooks(): void
    // Remove listener hooks for this handler
    removeHooks: function () {
        if (this._map) {
            // Clean up selected layers.
            this._featureGroup.eachLayer(this._disableLayerEdit, this);

            // Clear the backups of the original layers
            this._uneditedLayerProps = {};

            this._tooltip.dispose();
            this._tooltip = null;

            this._map
                .off('mousemove', this._onMouseMove, this)
                .off('touchmove', this._onMouseMove, this)
                .off('MSPointerMove', this._onMouseMove, this)
                .off(L.Draw.Event.EDITVERTEX, this._updateTooltip, this);
        }
    },

    // @method revertLayers(): void
    // Revert each layer's geometry changes
    revertLayers: function () {
        this._featureGroup.eachLayer(function (layer) {
            this._revertLayer(layer);
        }, this);
    },

    // @method save(): void
    // Save the layer geometries
    save: function () {
        var editedLayers = new L.LayerGroup();
        this._featureGroup.eachLayer(function (layer) {
            if (layer.edited) {
                editedLayers.addLayer(layer);
                layer.edited = false;
            }
        });
        this._map.fire(L.Draw.Event.EDITED, {layers: editedLayers});
    },

    _backupLayer: function (layer) {
        var id = L.Util.stamp(layer);

        if (!this._uneditedLayerProps[id]) {
            // Polyline, Polygon or Rectangle
            if (layer instanceof L.Polyline || layer instanceof L.Polygon || layer instanceof L.Rectangle) {
                this._uneditedLayerProps[id] = {
                    latlngs: L.LatLngUtil.cloneLatLngs(layer.getLatLngs())
                };
            } else if (layer instanceof L.Circle) {
                this._uneditedLayerProps[id] = {
                    latlng: L.LatLngUtil.cloneLatLng(layer.getLatLng()),
                    radius: layer.getRadius()
                };
            } else if (layer instanceof L.Marker || layer instanceof L.CircleMarker) { // Marker
                this._uneditedLayerProps[id] = {
                    latlng: L.LatLngUtil.cloneLatLng(layer.getLatLng())
                };
            }
        }
    },

    _getTooltipText: function () {
        return ({
            text: L.drawLocal.edit.handlers.edit.tooltip.text,
            subtext: L.drawLocal.edit.handlers.edit.tooltip.subtext
        });
    },

    _updateTooltip: function () {
        this._tooltip.updateContent(this._getTooltipText());
    },

    _revertLayer: function (layer) {
        var id = L.Util.stamp(layer);
        layer.edited = false;
        if (this._uneditedLayerProps.hasOwnProperty(id)) {
            // Polyline, Polygon or Rectangle
            if (layer instanceof L.Polyline || layer instanceof L.Polygon || layer instanceof L.Rectangle) {
                layer.setLatLngs(this._uneditedLayerProps[id].latlngs);
            } else if (layer instanceof L.Circle) {
                layer.setLatLng(this._uneditedLayerProps[id].latlng);
                layer.setRadius(this._uneditedLayerProps[id].radius);
            } else if (layer instanceof L.Marker || layer instanceof L.CircleMarker) { // Marker or CircleMarker
                layer.setLatLng(this._uneditedLayerProps[id].latlng);
            }

            layer.fire('revert-edited', {layer: layer});
        }
    },

    _enableLayerEdit: function (e) {
        var layer = e.layer || e.target || e,
            pathOptions, poly;

        // Back up this layer (if haven't before)
        this._backupLayer(layer);

        if (this.options.poly) {
            poly = L.Util.extend({}, this.options.poly);
            layer.options.poly = poly;
        }

        // Set different style for editing mode
        if (this.options.selectedPathOptions) {
            pathOptions = L.Util.extend({}, this.options.selectedPathOptions);

            // Use the existing color of the layer
            if (pathOptions.maintainColor) {
                pathOptions.color = layer.options.color;
                pathOptions.fillColor = layer.options.fillColor;
            }

            layer.options.original = L.extend({}, layer.options);
            layer.options.editing = pathOptions;

        }

        if (layer instanceof L.Marker) {
            if (layer.editing) {
                layer.editing.enable();
            }
            layer.dragging.enable();
            layer
                .on('dragend', this._onMarkerDragEnd)
                // #TODO: remove when leaflet finally fixes their draggable so it's touch friendly again.
                .on('touchmove', this._onTouchMove, this)
                .on('MSPointerMove', this._onTouchMove, this)
                .on('touchend', this._onMarkerDragEnd, this)
                .on('MSPointerUp', this._onMarkerDragEnd, this);
        } else {
            layer.editing.enable();
        }
    },

    _disableLayerEdit: function (e) {
        var layer = e.layer || e.target || e;

        layer.edited = false;
        if (layer.editing) {
            layer.editing.disable();
        }

        delete layer.options.editing;
        delete layer.options.original;
        // Reset layer styles to that of before select
        if (this._selectedPathOptions) {
            if (layer instanceof L.Marker) {
                this._toggleMarkerHighlight(layer);
            } else {
                // reset the layer style to what is was before being selected
                layer.setStyle(layer.options.previousOptions);
                // remove the cached options for the layer object
                delete layer.options.previousOptions;
            }
        }

        if (layer instanceof L.Marker) {
            layer.dragging.disable();
            layer
                .off('dragend', this._onMarkerDragEnd, this)
                .off('touchmove', this._onTouchMove, this)
                .off('MSPointerMove', this._onTouchMove, this)
                .off('touchend', this._onMarkerDragEnd, this)
                .off('MSPointerUp', this._onMarkerDragEnd, this);
        } else {
            layer.editing.disable();
        }
    },

    _onMouseMove: function (e) {
        this._tooltip.updatePosition(e.latlng);
    },

    _onMarkerDragEnd: function (e) {
        var layer = e.target;
        layer.edited = true;
        this._map.fire(L.Draw.Event.EDITMOVE, {layer: layer});
    },

    _onTouchMove: function (e) {
        var touchEvent = e.originalEvent.changedTouches[0],
            layerPoint = this._map.mouseEventToLayerPoint(touchEvent),
            latlng = this._map.layerPointToLatLng(layerPoint);
        e.target.setLatLng(latlng);
    },

    _hasAvailableLayers: function () {
        return this._featureGroup.getLayers().length !== 0;
    }
});